package com.zmkj.cn.sys.service.impl;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zmkj.cn.comm.baseenum.SeparatorEnum;
import com.zmkj.cn.comm.baseenum.WeChatErrorEnum;
import com.zmkj.cn.comm.constant.RedisConstant;
import com.zmkj.cn.comm.vo.WxAccessTokenVo;
import com.zmkj.cn.external.constant.WechatConstant;
import com.zmkj.cn.sys.dto.WxMiniprogram;
import com.zmkj.cn.sys.dto.WxMsgDto;
import com.zmkj.cn.sys.util.RedisUtil;
import com.zmkj.cn.sys.vo.input.WxSendInputVo;

import lombok.extern.slf4j.Slf4j;

/**
 * 微信模版消息推送
 *
 * @author 伍绍宇
 * @date 2021/04/29
 */
@Service
@Slf4j
public class WechatCommServiceImpl {

    @Value("${wx.accessToken.url}")
    private String accessTokenUrl;

    @Value("${wx.fwh.appId}")
    private String appId;

    @Value("${wx.fwh.secret}")
    private String secret;

    @Value("${wx.msg.url}")
    private String msgUrl;

    @Value("${wx.appId}")
    private String miniaAppId;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RestTemplate restTemplate;

    public static final String ACCESS_TOKEN = "accessToken";
    /**
     * 发送消息入口
     * @author 伍绍宇
     * @date 2021/04/29
     */
    public void sendEntrance(WxSendInputVo wxSendInputVo) {
    	if(StringUtils.isEmpty(wxSendInputVo.getUsers())) {
    		log.error("@推送微信通知信息-参数错误:用户名不能为空");
    	}
        Arrays.asList(wxSendInputVo.getUsers().split(SeparatorEnum.COMMA.code)).parallelStream().forEach(user -> {
            // 获取用户的服务号openid(跳过不存在的)
            if (!redisUtil.hashKey(RedisConstant.USER_OPEN.value, user)) {
            	log.error("@推送微信通知信息-参数错误:用户名({})不存在unionid",user);
                return;
            }
            // unionid
            String unionid = redisUtil.getMap(RedisConstant.USER_OPEN.value, user).toString();
            // 再查询是否绑定服务号
            if (!redisUtil.hashKey(RedisConstant.UNION_FWH_USER.value, unionid)) {
            	log.error("@推送微信通知信息-参数错误:用户名({})-unionid({})不存在openid",user,unionid);
                return;
            }
            
            WxMiniprogram wxMiniprogram= WxMiniprogram.builder().appid(miniaAppId).build();
            if(StringUtils.isNotBlank(wxSendInputVo.getPage())) {
            	wxMiniprogram.setPagepath(wxSendInputVo.getPage() + (StringUtils.isBlank(wxSendInputVo.getPageData()) ? "" : wxSendInputVo.getPageData()));
            }
            
            WxMsgDto wxMsgDto = WxMsgDto.builder()
                    .touser(redisUtil.getMap(RedisConstant.UNION_FWH_USER.value, unionid).toString())
                    .template_id(wxSendInputVo.getCode())
                    .data(JSONObject.parse(wxSendInputVo.getData().toString()))
                    .miniprogram(wxMiniprogram)
                    .build();
            
            String result = null;
            long startTime = System.currentTimeMillis();
            try {
            	//执行推送
            	result = sendCore(wxMsgDto);
            }catch(Exception e) {
            	log.error("@推送微信通知信息-异常:",e);
            }finally{
            	 log.info("@获取微信通知结果信息:响应时间{}ms,接收用户({}),请求参数({}),响应结果({})",(System.currentTimeMillis()-startTime),user,JSON.toJSONString(wxMsgDto),result);
            }
        });
    }

    /**
     * 发送消息core
     *
     * @author 伍绍宇
     * @date 2021/04/29
     */
    private String sendCore(WxMsgDto wxMsgDto) {
    	String result = null;
        //获取AccessToken
        String accessToken = createAccessToken();
        if(StringUtils.isNotEmpty(accessToken)) {
            ResponseEntity<String> responseEntity  = restTemplate.postForEntity(msgUrl, wxMsgDto, String.class, accessToken);
		    // 获取响应体
		    result = responseEntity.getBody();
		    JSONObject resultJson = JSONObject.parseObject(result);
		    Integer errcode = resultJson.getInteger(WechatConstant.ERRCODE.value);
		    String errMsg = resultJson.getString(WechatConstant.ERRMSG.value);
		    if (errcode != 0 && errcode != WeChatErrorEnum.ERROR_43101.getCode()) {
                if(errcode .equals(WeChatErrorEnum.ERROR_40001.getCode())){
                    log.warn("@微信通知响应信息-token失效,清楚token缓存,重试：{}",errMsg);
                    redisUtil.deleteKey(ACCESS_TOKEN);
                    return sendCore(wxMsgDto);
                }else{
		    	    log.error("@微信通知响应信息-失败：{},请求参数:{}",errMsg,wxMsgDto);
                }
		    }
        }else {
        	log.error("@获取微信accessToken信息-失败");
        }
        return result;
    }

    /**
     * 获取微信访问accessToken
     *
     * @author 伍绍宇
     * @date 2021/04/29
     */
    public String createAccessToken() {
        Object accToken = redisUtil.get(ACCESS_TOKEN);
        if (accToken == null) {
            Map<String, String> paramMap = new HashMap<>(2,1);
            paramMap.put(WechatConstant.SECRET.value, secret);
            paramMap.put(WechatConstant.APPID.value, appId);
            // 获取ACCESS_TOKEN
            String str = restTemplate.getForObject(accessTokenUrl, String.class, paramMap);
            log.info("@获取微信accessToken信息-响应数据：:" + str);
            WxAccessTokenVo wxAccessTokenVo = JSONObject.parseObject(str, WxAccessTokenVo.class);
            if (wxAccessTokenVo == null || wxAccessTokenVo.getErrcode() != null) {
            	return null;
            }
            //本地过期时间必须小于微信服务器时间
            if(wxAccessTokenVo.getExpires_in() != null && wxAccessTokenVo.getExpires_in().intValue() > 30) {
            	wxAccessTokenVo.setExpires_in(wxAccessTokenVo.getExpires_in() - 30);
            }
            redisUtil.set(ACCESS_TOKEN, wxAccessTokenVo.getAccess_token(), wxAccessTokenVo.getExpires_in());
            
            return wxAccessTokenVo.getAccess_token();
        } else {
            return String.valueOf(accToken);
        }
    }
}
